Copyright: Copyright 1996 PowerBASIC, Inc.


Table of Contents
PowerBASIC DLL Compiler Power Tips

Information for Visual Basic and PowerBASIC Programmers

Getting Help with PB/DLL

Articles

•	A quick start for Visual Basic Programmers

•	Working with Visual Basic Strings

•	Working with Visual Basic Arrays

•	PEEKing at DOS Memory

•	What is a Pointer?

PowerFacts

•	Can I compile my Visual Basic program with PB/DLL?

•	How do I fix a "File not found" error when I call a Sub or Function in my DLL?

•	Is it necessary to have a LibMain or WEP function in my DLL?

•	What is a resource file and how do I use it?

•	Visual Basic cannot find my PB/DLL Sub or Function

•	I'm getting a General Protection Fault when I pass a string to my DLL

•	I get extra characters at the end of my strings when I call PB/DLL

•	Converting Constants from VB to PB/DLL


What is a Pointer?

If you are new to programming, or have only programmed in Basic, then the concept of pointers may be completely foreign to you.  So, we're going to start with the basics and then move on to more advanced applications of pointers.  If you're a veteran programmer and know all about pointers, you may want to skip to the section entitled PowerBASIC Pointers.

So, what's a pointer?

Simply put, a pointer is a variable which holds the address of a location in memory.  It literally points to that location, hence the name pointer.  That, in itself may not sound very exciting, but wait until you see all the fun and powerful things you can do with them.

What are they good for?

The location pointed to by the pointer, is known as the target.  Since a pointer is a variable, its value is defined at run-time which lets you point to anything you want in memory.  Just like any other variable, declaring a pointer with the DIM statement only reserves storage for the pointer variable, but does not set its contents.  Before the pointer can be used for anything useful, it must be initialized (you must tell it what you want to point to).  This is a very important thing to remember.  Any pointer which has not been initialized to point to a known location is given a default value of &H0000 and is known as a NULL pointer.  Some of the most common (and hard to find) bugs are caused by using uninitialized pointers.  Using an uninitialized pointer can result in a General Protection Fault (GPF) in Windows.

PowerBASIC Pointers

PowerBASIC supports pointers to all of the supported data types, including user-defined TYPEs and UNIONs.  A pointer is declared with the DIM statement as follows:

DIM MyByte AS BYTE

DIM MyPtr AS BYTE PTR

The first DIM statement declares a BYTE variable named MyByte.  The second DIM statement reserves storage for a DWORD (4 bytes) variable named MyPtr, which can be used to point to one or more BYTEs.  In order to make MyPtr point to MyByte, it must be initialized with the address of MyByte.  That is done with the VARPTR function as follows:

MyPtr = VARPTR(MyByte)

This tells PowerBASIC to put the address of MyByte in the variable MyPtr.  Now that MyPtr has been initialized, it can be used to access MyByte for either reading from it or writing to it.  MyByte can accessed in one of two ways:

directly as in:

MyByte = 5

or indirectly using MyPtr as in:

@MyPtr = 5

Note the "@" (at sign) above.  This tells PowerBASIC that we want to access the target of the pointer instead of the pointer itself.  Without the leading "@", MyPtr would be set to the value of 5, instead of the value in MyByte being set to the value of 5.


A Quick Start
A Quick Start for Visual Basic Programmers

Although the PowerBASIC DLL Compiler (PB/DLL) can create standalone executable (.EXE) programs it was designed primarily as an add-on product for Visual Basic (VB) programmers.  This document will walk you through the steps necessary to extract parts of a 16-bit VB application, compile it into a standard machine-code DLL and then call it from your modified Visual Basic form.

Let's start with a VB program, made up of a text box, two buttons, and two routines that are called when the buttons are clicked.  The first button, labelled MaxInt, calls a function that compares two passed integers and returns the larger.  The other button, labelled CapFirst, calls a subroutine  that will capitalizes every word in a passed string.  The two routines might look something like this:

  Function MaxInt(ByVal Int1%, ByVal Int2%) As Integer

    If Int1% > Int2% Then

      MaxInt = Int1%

    Else

      MaxInt = Int2%

    End If

  End Function

  Sub CapFirst(zStr As String)

    Dim Curr As Integer

    Dim Prev As Integer

    Dim X As Integer

    Dim J As Integer

    Curr = 0

    Prev = 0

    J    = LEN(zStr)

    For X = 1 To J

      Curr = ASC(MID$(zStr(X)))              ' Get current character

      IF Prev = 32 Then                      ' If Prev = SPACE

        IF (Curr > 96) AND (Curr < 123) Then ' If Curr is lowercase

          MID$(zStr(X),1) = CHR$(Curr-32)    ' Convert to uppercase

        End If

      End If

      Prev = Curr

    Next

  End Sub

Let's extract these routines one at a time. The MaxInt function will serve to illustrate the process of moving a VB function to a PB source file and using PB/DLL to compile it into a DLL.  The first step is to save the selected VB form containing the MaxInt function as a text file: FORM1.TXT.  Then, using a text editor, like NOTEPAD for Windows, copy the MaxInt function from the FORM1.TXT file and save it as a new MYFIRST.BAS file.  We now have a file named MYFIRST.BAS containing a single function named MaxInt.  We need only make one addition and one modifications to this file, before it's ready to be compiled into a DLL.  The first one involves the addition of the $COMPILE metastatement. This tells PB/DLL that we want to make a DLL and not a stand-alone EXE. This metastatement must appear on a line by itself, before any executable statement in the file. The only other text that can appear on that line is a comment.  The second modification involves the addition of the EXPORT keyword, on the FUNCTION definition line, as follows.

  $COMPILE DLL                        ' necessary to compile to dll

  Function MaxInt(ByVal Int1%, ByVal Int2%) EXPORT As Integer

    If Int1% > Int2% Then

      MaxInt = Int1%

    Else

      MaxInt = Int2%

    End If

  End Function

As you can see, we only inserted the EXPORT keyword, right before the As Integer function return type specifier.  That tells PB/DLL to generate the proper code to make that function visible outside of the DLL.  If we don't use the EXPORT keyword, the function will work, but it will be a PRIVATE function, visible only inside the DLL and not accessible by any other program.

With those two modifications in place, we are now ready to compile our source into a DLL, callable from any windows program that can call a DLL, including VB.  You can compile the source in one of two ways:

Using the shell program PBSHELL:

1)	Double-click the PBSHELL icon to bring up the shell

2)	Click on the Basic file button and select MYFIRST.BAS

3)	Click on the Build button to compile MYFIRST.BAS.  The result window shows that the file is compiling.  The mouse cursor changes to an hourglass.  When compilation is complete, the mouse cursor changes back to an arrow, and the result window displays the results of the compilation.

4)	If there were any errors, click on the Edit button to bring up your favorite editor and correct the error.

5)	Repeat steps 3 and 4 above until there are no more errors.

Using the BUILD.BAT batch file (This will not work on PB/DLL versions after 1.1 unless using Win95 or later):

1)	Open a DOS window

2)	Change directory to where MYFIRST.BAS is located

3)	Type BUILD MYFIRST.  The compiler runs and displays the results on the screen

4)	If there were any errors detected, use your favorite editor and correct the error.

5)	Repeat steps 3 and 4 above until there are no more errors.

Once you get a successful compile, you should have on your disk a file named MYFIRST.DLL, containing an exported function named MaxInt. In order to call that function from your VB program, you need to add a declare statement (a prototype) for that function in the declarations section of your form and to comment out the extracted function as follows:

  Declare Function MaxInt% Lib "MYFIRST.DLL" (ByVal Int1%, ByVal Int2%)

  '''' Function MaxInt(ByVal Int1%, ByVal Int2%) As Integer

  ''''   If Int1% > Int2% Then

  ''''     MaxInt = Int1%

  ''''   Else

  ''''     MaxInt = Int2%

  ''''   End If

  '''' End Function

This line tells VB that you will be calling a function called MaxInt that resides in the library "MYFIRST.DLL".  If MYFIRST.DLL is not in the same directory as the VB program, you need to tell VB where to find it.  Here are some examples of how to do this:

1)	Place MYFIRST.DLL in the same directory as your VB code.

2)	Place MYFIRST.DLL in the Windows or Windows\System directory

3)	Specify a complete path on the Declare line (e.g. "C:\VBSource\MyFile\MYFILE.DLL")

4)	Do a ChDir to where the DLL is in your VB code.

Anyone of the 4 examples outlined above should work.  Once you've picked and implemented the method of your choice, you should be able to run your VB program and obtain the same results as you did, before the DLL was created.

Let us now take a look at the CapFirst Subroutine.  Moving that subroutine from our VB code into the DLL is not much more complicated than the MaxInt function.  We still need to go through the process of saving the form as a text file in order to be able to use a plain text editor to extract the parts we need, in this case the CapFirst sub. We will then add it to the MYFIRST.BAS file.  Once that is done, the only major difference is that this routine operates on strings rather than integers.  When VB passes a string to a DLL, it passes a pointer to a null-terminated copy of the string (known as an ASCIIZ string).  When you move CapFirst from VB to PB/DLL, the first change you need to make is in the way the function argument is declared.  In VB, CapFirst accepted a single argument of type String.  In PB/DLL, we need to change the String type to an ASCIIZ type. This tells PB/DLL that zStr is a null-terminated string.  The second modification is the addition of the EXPORT keyword, just like we did for MaxInt, on the sub definition line:

  Sub CapFirst(zStr As ASCIIZ) EXPORT

    Dim Curr As Integer

    Dim Prev As Integer

    Dim X As Integer

    Dim J As Integer

    Curr = 0

    Prev = 0

    J    = LEN(zStr)

    For X = 1 To J

      Curr = ASC(MID$(zStr(X)))              ' Get current character

      IF Prev = 32 Then                      ' If Prev = SPACE

        IF (Curr > 96) AND (Curr < 123) Then ' If Curr is lowercase

          MID$(zStr(X),1) = CHR$(Curr-32)    ' Convert to uppercase

        End If

      End If

      Prev = Curr

    Next

  End Sub

As said earlier, EXPORT makes the routine visible and callable from outside of the DLL.  We are now ready to compile our MYFIRST.BAS into a DLL containing two routines, the function MaxInt and the subroutine CapFirst.

As we saw earlier, the Vb form needs to be told where to find the CapFirst Sub.  Also we need to 'comment out' the original CapFirst souce lines. The declare statement and CapFirst source in VB will now look like this:

  Declare Sub CapFirst Lib "MYFIRST.DLL" (X$)

  '''' Sub CapFirst(zStr As String)

  ''''   Dim Curr As Integer

  ''''   Dim Prev As Integer

  ''''   Dim X As Integer

  ''''   Dim J As Integer

  ''''   Curr = 0

  ''''   Prev = 0

  ''''   J    = LEN(zStr)

  ''''   For X = 1 To J

  ''''     Curr = ASC(MID$(zStr(X)))              ' Get current character

  ''''     IF Prev = 32 Then                      ' If Prev = SPACE

  ''''       IF (Curr > 96) AND (Curr < 123) Then ' If Curr is lowercase

  ''''         MID$(zStr(X),1) = CHR$(Curr-32)    ' Convert to uppercase

  ''''       End If

  ''''     End If

  ''''     Prev = Curr

  ''''   Next

  '''' End Sub

The "Declare... " line, placed in the declaration section of the VB form, tells VB that we will be calling a sub named CapFirst that resides in library, MYFIRST.DLL, and accepts a singe string argument.  Once that is done, we should be able to run the VB program and call CapFirst with any string and get back the same string with the first character of every word converted to uppercase.

Now that we have done a basic conversion process, let's consider some of the optimizations at our disposal in the PB/DLL version of the source code.

First the entire Function MaxInt can be reduced to one line line this:

  Function MaxInt(ByVal Int1%, ByVal Int2%) As Integer

    FUNCTION = MAX(Int1%, Int2%)

  End Function

A couple of things worth noticing here:

•	The use of the FUNCTION keyword instead of the function's name.  This allows a change to the name of the function without having to change name(s) where return values are assigned.

•	The use of the PB/DLL built-in MaxInt function to compare the two integers and return the larger of the two.  The MAX function can accept more than two integers, and return the larger of the group.  There is also a companion MIN function, as well as string versions (MAX$ and MIN$).

In the CapFirst subroutine, we can use PB/DLL's BYTE data type to hold the previous and current character.  We can also use a more efficient pointer variable to scan the string instead of the slower MID$ function.  Finally, by using a pointer, makes it un-necessary to use the ASC function, the MID$ statement and the CHR$ function, as follows:

  SUB CapFirst(zStr AS ASCIIZ) EXPORT

    DIM Curr AS BYTE

    DIM Prev AS BYTE

    DIM X    AS INTEGER

    DIM J    AS INTEGER

    DIM bPtr AS BYTE PTR

    Curr = 0                                    ' Initialize

    Prev = 0

    J    = LEN(zStr)

    bPtr = VARPTR(zStr)

    FOR X = 0 To J-1

      Curr = @bPtr[X]                           ' Get current character

      IF Prev = 32 THEN                         ' If Prev = SPACE and

        IF (Curr > 96) AND (Curr < 123) THEN    '   If Curr is lowercase

          @bPtr[X] = Curr-32                    '     Convert to UPPERCASE

        END IF

      END IF

      Prev = Curr                               ' Set previous

    NEXT

  END SUB

This version of CapFirst does the same as the previous version, except that it is faster while showing off some of the added functionality (i.e. data pointers) found in PB/DLL.


Working with VB Arrays
Working with Visual Basic Arrays

There are a lot of similarities between working with VB strings and working with VB arrays.  Though PB/DLL sports an impressive number of intrinsic array functions, those functions only work with native PB/DLL arrays.  In fact, even simple accesses, like reading or writing a single element of a VB array (i.e.  X = VBARRY(I)) will not work, as the internal array engines are not compatible with each other.  If you need to access VB arrays in a DLL written in PB/DLL, you have two options:

  1)	Copy the VB array into a native PB/DLL array and then use PB/DLL's array functions to operate on the array.

  2)	Call the VB API to obtain information about the VB array.

By the way, even using option 1, you may still have to call the VB API if the VB array has more than one dimension or is larger than 64K.

Let's look at a simple example, using option 1 to sort a VB integer array with one dimension.

In Visual Basic

  GLOBAL CONST N_ELEMS = 10000

  GLOBAL MyIntArray() AS INTEGER

  Declare Sub pbSortIntArray LIB "pbsort.dll" (intArray%, BYVAL nElems%)

  SUB MyVbSub

    REDIM MyIntArray(N_ELEMS) AS INTEGER

    CALL pbSortIntArray(MyIntArray(0), N_ELEMS)

  END SUB

In the DLL

  $COMPILE DLL

  SUB pbSortIntArray(BYVAL intArray AS DWORD, _

                     BYVAL nElems AS INTEGER) EXPORT

  DIM vbArray         AS INTEGER PTR

  DIM pbArray(nElems) AS INTEGER

  DIM X               AS INTEGER

  '- assign address of first element to vbArray

  vbArray = intArray

  '- Copy vbArray into a native PB/DLL array

  FOR X = 0 TO nElems

    pbArray(X) = @vbArray[X]

  NEXT

  ' Sort the Pb/DLL array

  ARRAY SORT pbArray() FOR nElems

  '- Copy PB/DLL array back into the vbArray

    FOR X = 0 TO nElems

      @vbArray[X] = pbArray(X)

    NEXT

  END SUB

Let's take this routine apart.  First of all, you will notice that the array is declared as a ByVal DWORD.  That is because VB passes the first element of the array ByRef (i.e.  a pointer to the data for the first element of the array).  We then dimension vbArray as an integer pointer, pbArray() as a native PB/DLL integer array, and finally an integer x which is used as an array index.  The first statement vbArray = intArray, assigns the passed pointer to the typed integer pointer, which in this case, will be used as in indexed pointer.  Indexed pointers use c-style square brackets "[]" instead of the familiar parentheses, for array access.  Indexed pointer addressing is always zero-based, meaning that the first element of the array is always element zero.  We then go into the first loop, which simply copies the VB array into the local PB/DLL array.  Next is the single line that does that actual sorting of the array.  Finally, there's the second loop which copies the sorted PB/DLL array back into the VB array.

Upon returning from the call, the VB array will have been sorted in acending order (the default direction).  Array sort also supports sorting in descending order and even the use of a TAG-ALONG array, whose elements will be sorted in the same order as those of the original master array.  PB/DLL also supports sorting string arrays, in ascending or descending order, with the option of treating lower case characters as equal to their uppercase counterparts.  Finally, string sorting can accomodate an optional collate string, with 256 characters, which dictates an arbitrary sorting order.  In addition to sorting, PB/DLL supports ARRAY INSERT, DELETE and SCAN operations which insert and delete an element, or search an array for an element that satisfies the specified criteria.

The pbSortIntArray routine can easily be adapted to work with other data types, such as LONGs, SINGLEs and even user-defined types.  This routine, along with a LONG and SINGLE sort routine are included on the PB/DLL distribution disk as filename VBSORT.BAS, in the EXAMPLES subdirectory.

When called from VB, the sort routine first copies the VB array into a native PB array.  The PB array is then quickly sorted using PB/DLL's built-in ARRAY SORT command.  The PB array is then copied back into the VB array and the routine returns.  Depending on the size of the array in question, the overhead of copying the array back and forth, may counteract the speed advantage gained by compiling and using PB/DLL's Array commands.  Also, remenber that this method will only work with a VB array with a single dimension and whose data occupies less than 64K.

Using method #2 involves calling the VB API to obtain information about the array.  The VB API provides the following functions to support array accesses in DLL and custom controls:

vbArrayBounds	Returns upper and lower bound of an array.

vbArrayElement	Returns a pointer to the value of specified element.

vbArrayElemSize	Returns the size of an array element.

vbArrayFirstElem	Returns a pointer to the first element of an array.

vbArrayIndexCount	Returns the number of indexes for a VB array.

A discussion of how to use the above functions is beyond the scope of this article.  Refer to the VBAPI.HLP file that's part of the CDK that comes with the professional version of Visual Basic for further information.  That file includes the DECLARE statements you will need in order to call those functions from your DLL or custom control.


Working with VB Strings
Working with Visual Basic Strings

The Visual Basic API provides functions for manipulating 2 types of strings:

•	HLSTR	Native VB variable-length string (also called LPSTR)

•	HSZ	Dynamically allocated C-style strings (also called LPCSTR)

Although PB/DLL supports its own brand of variable-length strings, they are not directly compatible with VB's HLSTR strings.  VB's HLSTR string type is mostly used internally by VB programs and VBX custom controls.  When a VB application calls a function in a DLL which expects a string as argument, the string is usually passed ByVal.  What the DLL actually gets is a pointer to a C-style (i.e. null-terminated) string.

Working with HSZ or ASCIIZ Strings

This is just fine since PB/DLL has native support for C-style strings, called ASCIIZ strings (HSZ in VB parlance).  Just declare the passed string as ASCIIZ and PB/DLL will automatically know how to access it.  In your DLL routine you can modify the passed string as long as you don't write past the end of the string.  Doing so, would most likely result in a General Protection fault.  If you modify the string in the DLL and the resulting string is shorter than the original, you will need to trim off the extra characters at the end of the string on the VB side.  In both VB3 and VB4 16-bit, the recommended safe way of passing a string to a DLL is to pre-allocate a string of sufficient length and pass it ByVal to the DLL.

In Visual Basic:

  Declare Sub MyDllSub Lib "MYDLL.DLL" (ByVal x$)

  Dim MyString AS STRING

  SUB MySub

    MyString = SPACE$(255)

    CALL MyDllSub(MyString)

    Debug.Print MyString

  END SUB

In the DLL:

  SUB MyDllSub(TheString AS ASCIIZ) EXPORT

    TheString = "ABCD"

  END SUB

Upon return from the DLL, MyString contains "ABCD" followed by one null (zero) character and  250 space characters.  The extra null characters can be removed from the end of the string, if required, by adding to the VB program the following line:

  MyString = Left$(MyString, InStr(MyString, Chr$(0)) - 1)

An example:

  The following routine capitalize every word in a string passed from VB.

In Visual Basic

  Declare Sub CapFirst Lib "myfirst.dll" (ByVal X$)

  Dim MyString AS STRING

  SUB MySub

    MyString = "this is a test of passing a string to a dll"

    Debug.Print MyString

    Capfirst MyString

    Debug.Print MyString

  END SUB

In the DLL

  $COMPILE DLL

  SUB CapFirst(zStr AS ASCIIZ) EXPORT

    DIM Letter AS BYTE PTR

    DIM Last   AS BYTE

    Last   = 32

    Letter = VARPTR(zStr)

    WHILE (Last <> 0)  ' Loop until terminating null

      IF Last = 32 THEN

       Last = @Letter

         IF (Last > 96) AND (Last < 123) THEN  ' if lowercase

          @Letter = Last - 32                  ' convert

        END IF

      ELSE

        Last = @Letter

      END IF

      INCR Letter

    WEND

  END SUB

Let's take this routine apart.  First of all, you will notice that the passed string is declared AS ASCIIZ in the DLL.  We then DIM Letter as a BYTE pointer and Last as a BYTE.  We'll use Letter to walk through the string and Last to keep track of the last character we looked at.  Last is initialized to 32 (the ASCII code for SPACE).  We only want to capitalize a character that follows a space character.  Letter is intialized to point at the beginning of the string.  We then loop until we reach the null terminator.  Inside the loop, Last is assigned the current character.  If the character is a lowecase character, we subtract 32 to convert it to uppercase.  Finally, we increment Letter to point to the next character in the string.  This goes on until we find the terminating nul (Chr$(0)).

Working with VB's HLSTR Strings

As said earlier, PB/DLL doesn't natively support VB's HLSTR strings.  However, if you need to, you can pass a native (HLSTR) VB string to a PB/DLL routine.  When such a string is passed by reference (the default mode), the DLL gets passed the handle to the VB string.  In order to access the string's data, you need to call the VB API to get information about the string.  The VB API contains the following 2 functions that return information about a VB HLSTR:

vbDerefHlStr	Returns a pointer to the strings data, in VB-managed memory space.

vbDerefHlStrLen	Returns the string's length, in addition to its location.

PB/DLL ships with two functions in the VBAPI.INC file that make accessing VB HLSTR string a bit easier:

vbStringToPb	Copies a VB HLSTR string into a native PB/DLL string.

pbStringToVb	Copies a PB/DLL string into a VB HLSTR that can be returned to VB.

The VB API contains a number of other functions that operate on VB's HLSTR string type.  Among them are:

vbCreateHlStr	Create a Visual Basic string and return a handle.

vbCreateTempHlStr	Create a temporary Visual Basic string and return a handle.

vbDestroyHlStr	Destroy a VB string

vbDerefZeroTermHlStr	Return pointer to string's data

vbGetHlStr	Copy HLSTR into provided buffer

vbGetHlStrLen	Returns length of spefied HLSTR string

vbSetHlstr	Copy data from buffer to HLSTR

vbResizeHlstr	Change the length of an existing HLSTR string.

The VB API also contains a number of functions that facilitate access to VB's HSZ string type.  Among them are:

vbCreateHsz	Allocate space for a new HSZ string, returns handle

vbDestroyHsz	Destroy a previously allocated HSZ string

vbDerefHsz	Return pointer to HSZ string data

vbLockHsz	Lock HSZ string's buffer

vbUnlockHsz	Unlock HSZ string's buffer

All of the functions mentioned above are fully documented in the VBAPI.HLP file that comes with the Professional version of Visual Basic.

When working with HLSTR strings Keep in mind the fact that VB strings can be moved around by Visual Basic.  When you obtain a pointer to a VB string, it is only valid as long as you don't call any VB API or Windows API functions that might cause the string to be moved.  When in doubt, it's best to call the VB API to obtain a fresh valid pointer to the string's data, prior to operating on it.


File not found Error
How do I fix a "File not found" error when I call a Sub or Function in my DLL?

A "File not found" error in Visual Basic can be caused in one of three ways.

1)  In the Declare statement in your Visual Basic code you have used an invalid filename or pathname.  Double check to make sure the path and filename you have specified is correct.

2)  You did not specify a pathname in the Declare statement when one was required.  This can happen if you put just a DLL name and your DLL is not in the current default directory, or Windows can't find it in its search path.  When you make a call for a DLL which is not already loaded into memory, Windows will search for the DLL in the following order:

1.  The current directory.

2.  The WINDOWS directory.

3.  The WINDOWS\SYSTEM directory.

4.  The directories listed in your DOS PATH.

3)  In the PB/DLL source code you specified a path or file name in your Declare statement and not a module name.  Visual Basic dynamically loads DLLs into memory as they are called (this is called "explicit" linking) and therefore you are required to specify the name of the DLL file inside of the quotes:

Declare Sub MySub Lib "MY.DLL" ()

However in PB/DLL, external DLLs are loaded into memory at the time your DLL is loaded into memory.  This is called "implicit" linking.  Therefore only the module name of the DLL is used in the Declare statement:

Declare Sub MySub Lib "MY" ()

See page 13 of your PB/DLL manual for more information on the difference between Explicit and Implicit linking and page 102 of your PB/DLL manual for more information on the Declare statement.


LibMain/WEP
Is it necessary to have a LibMain or WEP function in my DLL?

No, it is not necessary to have LibMain or WEP functions in your DLL.

The LibMain function is called by Windows ONLY when the DLL is first loaded in memory.  It is recommended that you include one in your DLLs to ensure compatibility with future versions of Microsoft Windows.  You should never call LibMain yourself.

Note: If your DLL includes a LibMain function, it's important to ensure that it returns a 1 as the function's return value.  On some versions of Windows, you may get an Out of Memory message if a value other than 1 is returned.

WEP is not currently used by Windows.  However, it is a good idea to include a dummy Wep function, even if your DLL doesn't need one, to ensure compatibility with future versions of Microsoft Windows.

Example:

$COMPILE DLL

FUNCTION LibMain (BYVAL hInstance AS INTEGER, _

                  BYVAL wDataSeg AS WORD, _

                  BYVAL wHeapSize AS WORD, _

                  lpszCmdLine AS ASCIIZ) EXPORT AS INTEGER

  '

  '  Your code goes here

  '

  LibMain = 1   'success!

END FUNCTION

FUNCTION Wep (BYVAL nParameter AS INTEGER) EXPORT AS WORD

  Wep = 1       'success!

END FUNCTION


Resource Files
What is a resource file and how to I use it?

A resource is a collection of data which an application uses at run-time. This collection of data can include icons, bitmaps, menus, dialog boxes, and more.  It is usually compiled by a resource compiler into binary form from a resource script and embedded into an executable or DLL.

A resource script is a set of instructions which tells the resource compiler what kinds of resources to add to your application.  To simply add an icon to your application, your resource might look like this:

RESTART ICON RESTART.ICO

'RESTART'     is the name of the resource.

'ICON'        is the type of resource, in this case an icon.

'RESTART.ICO' is the name of the icon file to use.

If the resource filename is RESTART.RC and the executable filename is RESTART.EXE then you need only run the resource compiler (RC.EXE) with the name of the resource file:

C:\> RC RESTART

This will compile the resource script into a binary resource file called RESTART.RES and then embed that into the executable.

Resources are valuable because they require less memory when your application is running.  When a resource is not being used, Windows can swap it to disk making more room when additional memory is required.  Additionally, when writing multi-language applications you can change the resource file as necessary without having to recompile the application.

For more information on using resource files with your applications and DLLs, see the Microsoft SDK documentation or the book "Microsoft Windows Programming Tools" (ISBN 1-55615-310-4).


Compile VB Code
Can I compile my Visual Basic Program with PB/DLL?

PB/DLL is not a replacement for Visual Basic, it is an enhancement tool.  It allows you to take the slow parts of your code (typically calculations, binary/sequential/random access file I/O, and Windows API calls) and compile that into fast machine code DLLs which you can then call from within your Visual Basic program.  The result is an overall performance gain in your application.

While PB/DLL is a Basic compiler, it is not 100% compatible with Visual Basic.  It does not support forms or Visual Basic objects such as data controls (for database access).


Getting Help
Getting Help with PB/DLL

Having a problem with PB/DLL?  Couldn't find the answer in the manual or the README.TXT file?  There are many other ways you can get help with your PB/DLL questions.

Internet

If you have access to the Internet, be sure to visit our home page at http://www.powerbasic.com for the latest news, information on upgrades and programming tips.  You can send electronic mail to our support staff at support@powerbasic.com.  Example files and the latest PowerTips can be found at our FTP site ftp.powerbasic.com.

CompuServe Information Service

You can ask questions, make comments and discuss PB/DLL with other programmers through our forum on CompuServe.  If you are a CompuServe member, type GO POWERBASIC to join in the fun.  If you are not yet a member, but you have a modem, you can call Representative #194 at 1-800-848-8199.  In the United Kingdom, call 0800-29-9378.  In Germany, call 0130-37-32.  In the rest of Europe, call 44-272-255-111.  Outside the US, Canada, and Europe call 614-457-0802.  Setup is free and you'll receive a $15 usage credit.

Electronic Bulletin Board System

PowerBASIC, Inc. maintains a BBS system which you can call into with your 2400 baud through 28.8k baud modem.  Just set your com port to 8 bits, No parity and 1 stop bit (8/N/1) and dial 408-659-7401.

Fax

You can FAX a description of your problem, including a code fragment that illustrates the problem, to the attention of Technical Support at (408) 659-8008.  Be sure to include your return fax number (including country code if outside the U.S. and Canada).

Phone

You can also telephone our Technical Support department at (408) 659-8000, Monday through Friday between 9:00 am and 5:00 pm Pacific Time.

Important:  If you contact our Technical Support department by Email, Fax or Telephone please provide your Name, Telephone Number and Serial Number (located on your master disk).


Cannot Find Sub or Function
Visual Basic cannot find my PB/DLL Sub or Function

By default, all Subs and Functions in a DLL are private.  This prevents programs which use the DLL from calling any code which you don't want them to have access to.  To allow a program to access a Sub or Function, you must include the EXPORT keyword on the Sub or Function definition line:

Sub MyDllSub() Export    'make it available to VB

End Sub

Sub MyPrivateDllSub()    'VB can't see this Sub

End Sub


GPF Passing Strings
I'm getting a General Protection Fault when I pass a string to my DLL

Dynamic strings in Visual Basic and PB/DLL use different string handling engines to manage them.  A Visual Basic string handle is not compatible with a PB/DLL string handle and vice-versa.

When you pass a Visual Basic string to your DLL, you need to pass it "ByVal" so that VB passes an ASCIIZ string.  Your DLL should then use the ASCIIZ string type in its procedure prototype.  Example:

In Visual Basic:

Declare Sub MyDllSub Lib "MYDLL.DLL" (ByVal x$)

In PB/DLL:

Sub MyDLLSub(x As Asciiz) Export

If you need to return a value from the Sub, you need to make certain that the string you pass has enough room to hold the value returned.

Sub Button1()

  x$ = Space$(255)  'make enough room

  MyDllSub x$

End Sub


Extra Charactors in strings
I get extra characters at the end of my strings when I call PB/DLL

When Visual Basic passes a string by value to a DLL, it actually passes a pointer to the null terminated data for the string.  If the buffer is large enough, you can pass a return value back to VB from within your DLL.  However, when control returns back to the VB program, Visual Basic does not trim off all of the extra bytes after the null character.  You can do this yourself with the following Visual Basic code:

'This Function will trim off all characters after the null terminator

Function TrimZ$ (Text$)

  TrimZ$ = Trim$(Left$(Text$, InStr(Text$, Chr$(0)) - 1))

End Function

Example:

Declare Sub MyDllSub Lib "MYDLL.DLL" (ByVal x$)

Sub Button1()

  x$ = String$(255, " ")  'make enough room

  MyDllSub x$

  x$ = TrimZ$( x$ )       'trim off extra characters

End Sub


Constants
Converting Constants from VB to PB/DLL

PB/DLL only supports named Integer constants directly.  An integer constant is declared by starting the name with a percent sign "%" as in:

  %MyConst = 42

However, this doesn't mean you can't have any other constant values in your PB/DLL code.  You can simulate floating point and string constants by declaring a variable of the desired type and assigning a value to it.  The best way to do this is to define all of your constants as GLOBAL variables (constants have global scope anyway) and initialize all of them in either your LibMain function (in case of a DLL) or your WinMain function (in case of an EXE).   An example of doing this is shown below:

Let's say you have the following two constants in VB:

  GLOBAL CONST AppName = "The Best"

  GLOBAL CONST AppVers = 1.1

In PB/DLL you would do this:

  GLOBAL AppName AS STRING

  GLOBAL AppVer  AS SINGLE

Then, in your LibMain or WinMain, you would initialize them:

  AppName = "The Best"

  AppVers = 1.1

Since by definition, a constant cannot change value at run-time, you must ensure that your code doesn't accidentally change the value assigned to one of your pseudo-constants.


PEEKing at DOS Memory

Microsoft attained its ultimate goal of making Windows programming easier with Visual Basic for Windows.  But in the process they left out a few sorely needed features.  One such feature is memory access using PEEK and POKE.

PB/DLL supports both PEEK and POKE to access memory addresses, but not in the traditional method of DOS Basic programming.  In DOS you would first use the DEF SEG statement to set the default memory segment you wanted to access and the use PEEK and POKE to access the offset within that segment.   This works fine in DOS where you are limited to one megabyte in total memory addressing.  But Windows runs in protected mode which is a whole new ballgame.

Microsoft Windows has to be able to multitask many different applications.  Usually very large applications which demand large amounts of memory.  The one megabyte limitation of real mode DOS just won't do, so Windows runs in 16-bit protected mode.  This allows Windows to address up to 16 megabytes of memory.  And it preserves the 64k segmented memory structure familiar to most DOS programmers.  However, instead of segments and offsets, Windows uses selectors and offsets.

A selector is a 16-bit index into a table of 32-bit memory addresses.  When a value is placed into one of the CPU segment registers an exception error occurs.  The memory manager in Windows traps this error and compares the value in the register against its list of valid selectors.  If the value is not a valid selector a General Protection Fault (GPF) is triggered.  (Invalid selectors or memory addresses account for most of the GPFs which occur in Windows programs.)

In order to access a memory address, a valid 16-bit selector must be placed into one of the segment registers.  Placing a DOS segment value into a register will cause a GPF because its not a valid selector.

So, how do you access DOS segments from your Windows application then?  Quite simply you ask Windows for a valid selector for the region of DOS memory you want to access.  Windows itself already keeps a list of selector values handy for the most common DOS memory segments.  And PB/DLL provides pre-defined variables for each of these selectors:

pbv0000H  =  0000:0000

pbv0040H  =  0040:0000

pbvA000H  =  A000:0000

pbvB000H  =  B000:0000

pbvB800H  =  B800:0000

pbvC000H  =  C000:0000

pbvD000H  =  D000:0000

pbvE000H  =  E000:0000

pbvF000H  =  F000:0000

PEEK and POKE in PB/DLL require 32-bit pointers in order to access memory.  A pointer is simply a 16-bit selector combined with a 16-bit offset into a 32-bit address which points at a given memory location.

So, to access the I/O address of the first LPT port which is in memory at 0040:0008 we would combine the selector for 0040h in the variable pbv0040H and the offset of 8 into a 32-bit pointer using the following formula:

Selector = pbv0040H

Offset   = 8

Pointer  = Selector * 65536 + Offset

The I/O address is two bytes, so we would use the following formula along with PEEK to calculate it:

Lpt1Address = PEEK(Pointer+1) * 256 + PEEK(Pointer)

Remember, like in DOS, you are limited to accessing a maximum of 64k of memory with any given selector.  To access memory beyond 64k from a given selector you need a new selector.